// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function() {
  var config = { tabSize: 4, indentUnit: 2 };
  var mode = CodeMirror.getMode(config, 'markdown');
  function MT(name) {
    test.mode(name, mode, Array.prototype.slice.call(arguments, 1));
  }
  var modeHighlightFormatting = CodeMirror.getMode(config, {
    name: 'markdown',
    highlightFormatting: true,
  });
  function FT(name) {
    test.mode(
      name,
      modeHighlightFormatting,
      Array.prototype.slice.call(arguments, 1),
    );
  }
  var modeMT_noXml = CodeMirror.getMode(config, {
    name: 'markdown',
    xml: false,
  });
  function MT_noXml(name) {
    test.mode(name, modeMT_noXml, Array.prototype.slice.call(arguments, 1));
  }
  var modeMT_noFencedHighlight = CodeMirror.getMode(config, {
    name: 'markdown',
    fencedCodeBlockHighlighting: false,
  });
  function MT_noFencedHighlight(name) {
    test.mode(
      name,
      modeMT_noFencedHighlight,
      Array.prototype.slice.call(arguments, 1),
    );
  }
  var modeAtxNoSpace = CodeMirror.getMode(config, {
    name: 'markdown',
    allowAtxHeaderWithoutSpace: true,
  });
  function AtxNoSpaceTest(name) {
    test.mode(name, modeAtxNoSpace, Array.prototype.slice.call(arguments, 1));
  }
  var modeOverrideClasses = CodeMirror.getMode(config, {
    name: 'markdown',
    strikethrough: true,
    emoji: true,
    tokenTypeOverrides: {
      header: 'override-header',
      code: 'override-code',
      quote: 'override-quote',
      list1: 'override-list1',
      list2: 'override-list2',
      list3: 'override-list3',
      hr: 'override-hr',
      image: 'override-image',
      imageAltText: 'override-image-alt-text',
      imageMarker: 'override-image-marker',
      linkInline: 'override-link-inline',
      linkEmail: 'override-link-email',
      linkText: 'override-link-text',
      linkHref: 'override-link-href',
      em: 'override-em',
      strong: 'override-strong',
      strikethrough: 'override-strikethrough',
      emoji: 'override-emoji',
    },
  });
  function TokenTypeOverrideTest(name) {
    test.mode(
      name,
      modeOverrideClasses,
      Array.prototype.slice.call(arguments, 1),
    );
  }
  var modeFormattingOverride = CodeMirror.getMode(config, {
    name: 'markdown',
    highlightFormatting: true,
    tokenTypeOverrides: {
      formatting: 'override-formatting',
    },
  });
  function FormatTokenTypeOverrideTest(name) {
    test.mode(
      name,
      modeFormattingOverride,
      Array.prototype.slice.call(arguments, 1),
    );
  }

  FT(
    'formatting_emAsterisk',
    '[em&formatting&formatting-em *][em foo][em&formatting&formatting-em *]',
  );

  FT(
    'formatting_emUnderscore',
    '[em&formatting&formatting-em _][em foo][em&formatting&formatting-em _]',
  );

  FT(
    'formatting_strongAsterisk',
    '[strong&formatting&formatting-strong **][strong foo][strong&formatting&formatting-strong **]',
  );

  FT(
    'formatting_strongUnderscore',
    '[strong&formatting&formatting-strong __][strong foo][strong&formatting&formatting-strong __]',
  );

  FT(
    'formatting_codeBackticks',
    '[comment&formatting&formatting-code `][comment foo][comment&formatting&formatting-code `]',
  );

  FT(
    'formatting_doubleBackticks',
    '[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]',
  );

  FT(
    'formatting_atxHeader',
    '[header&header-1&formatting&formatting-header&formatting-header-1 # ][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]',
  );

  FT(
    'formatting_setextHeader',
    '[header&header-1 foo]',
    '[header&header-1&formatting&formatting-header&formatting-header-1 =]',
  );

  FT(
    'formatting_blockquote',
    '[quote&quote-1&formatting&formatting-quote&formatting-quote-1 > ][quote&quote-1 foo]',
  );

  FT(
    'formatting_list',
    '[variable-2&formatting&formatting-list&formatting-list-ul - ][variable-2 foo]',
  );
  FT(
    'formatting_list',
    '[variable-2&formatting&formatting-list&formatting-list-ol 1. ][variable-2 foo]',
  );

  FT(
    'formatting_link',
    '[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string&url (][string&url http://example.com/][string&formatting&formatting-link-string&url )]',
  );

  FT(
    'formatting_linkReference',
    '[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string&url [][string&url bar][string&formatting&formatting-link-string&url ]]]',
    '[link&formatting&formatting-link [][link bar][link&formatting&formatting-link ]]:] [string&url http://example.com/]',
  );

  FT(
    'formatting_linkWeb',
    '[link&formatting&formatting-link <][link http://example.com/][link&formatting&formatting-link >]',
  );

  FT(
    'formatting_linkEmail',
    '[link&formatting&formatting-link <][link user@example.com][link&formatting&formatting-link >]',
  );

  FT('formatting_escape', '[formatting-escape \\*]');

  FT(
    'formatting_image',
    '[formatting&formatting-image&image&image-marker !][formatting&formatting-image&image&image-alt-text&link [[][image&image-alt-text&link alt text][formatting&formatting-image&image&image-alt-text&link ]]][formatting&formatting-link-string&string&url (][url&string http://link.to/image.jpg][formatting&formatting-link-string&string&url )]',
  );

  FT(
    'codeBlock',
    '[comment&formatting&formatting-code-block ```css]',
    '[tag foo]',
    '[comment&formatting&formatting-code-block ```]',
  );

  MT('plainText', 'foo');

  // Don't style single trailing space
  MT('trailingSpace1', 'foo ');

  // Two or more trailing spaces should be styled with line break character
  MT('trailingSpace2', 'foo[trailing-space-a  ][trailing-space-new-line  ]');

  MT(
    'trailingSpace3',
    'foo[trailing-space-a  ][trailing-space-b  ][trailing-space-new-line  ]',
  );

  MT(
    'trailingSpace4',
    'foo[trailing-space-a  ][trailing-space-b  ][trailing-space-a  ][trailing-space-new-line  ]',
  );

  // Code blocks using 4 spaces (regardless of CodeMirror.tabSize value)
  MT('codeBlocksUsing4Spaces', '    [comment foo]');

  // Code blocks using 4 spaces with internal indentation
  MT(
    'codeBlocksUsing4SpacesIndentation',
    '    [comment bar]',
    '        [comment hello]',
    '            [comment world]',
    '    [comment foo]',
    'bar',
  );

  // Code blocks should end even after extra indented lines
  MT(
    'codeBlocksWithTrailingIndentedLine',
    '    [comment foo]',
    '        [comment bar]',
    '    [comment baz]',
    '    ',
    'hello',
  );

  // Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value)
  MT('codeBlocksUsing1Tab', '\t[comment foo]');

  // No code blocks directly after paragraph
  // http://spec.commonmark.org/0.19/#example-65
  MT('noCodeBlocksAfterParagraph', 'Foo', '    Bar');

  MT('codeBlocksAfterATX', '[header&header-1 # foo]', '    [comment code]');

  MT(
    'codeBlocksAfterSetext',
    '[header&header-2 foo]',
    '[header&header-2 ---]',
    '    [comment code]',
  );

  MT(
    'codeBlocksAfterFencedCode',
    '[comment ```]',
    '[comment foo]',
    '[comment ```]',
    '    [comment code]',
  );

  // Inline code using backticks
  MT('inlineCodeUsingBackticks', 'foo [comment `bar`]');

  // Block code using single backtick (shouldn't work)
  MT('blockCodeSingleBacktick', '[comment `]', '[comment foo]', '[comment `]');

  // Unclosed backticks
  // Instead of simply marking as CODE, it would be nice to have an
  // incomplete flag for CODE, that is styled slightly different.
  MT('unclosedBackticks', 'foo [comment `bar]');

  // Per documentation: "To include a literal backtick character within a
  // code span, you can use multiple backticks as the opening and closing
  // delimiters"
  MT('doubleBackticks', '[comment ``foo ` bar``]');

  // Tests based on Dingus
  // http://daringfireball.net/projects/markdown/dingus
  //
  // Multiple backticks within an inline code block
  MT('consecutiveBackticks', '[comment `foo```bar`]');

  // Multiple backticks within an inline code block with a second code block
  MT('consecutiveBackticks', '[comment `foo```bar`] hello [comment `world`]');

  // Unclosed with several different groups of backticks
  MT('unclosedBackticks', '[comment ``foo ``` bar` hello]');

  // Closed with several different groups of backticks
  MT('closedBackticks', '[comment ``foo ``` bar` hello``] world');

  // info string cannot contain backtick, thus should result in inline code
  MT('closingFencedMarksOnSameLine', '[comment ``` code ```] foo');

  // atx headers
  // http://daringfireball.net/projects/markdown/syntax#header

  MT('atxH1', '[header&header-1 # foo]');

  MT('atxH2', '[header&header-2 ## foo]');

  MT('atxH3', '[header&header-3 ### foo]');

  MT('atxH4', '[header&header-4 #### foo]');

  MT('atxH5', '[header&header-5 ##### foo]');

  MT('atxH6', '[header&header-6 ###### foo]');

  // http://spec.commonmark.org/0.19/#example-24
  MT('noAtxH7', '####### foo');

  // http://spec.commonmark.org/0.19/#example-25
  MT('noAtxH1WithoutSpace', '#5 bolt');

  // CommonMark requires a space after # but most parsers don't
  AtxNoSpaceTest('atxNoSpaceAllowed_H1NoSpace', '[header&header-1 #foo]');

  AtxNoSpaceTest('atxNoSpaceAllowed_H4NoSpace', '[header&header-4 ####foo]');

  AtxNoSpaceTest('atxNoSpaceAllowed_H1Space', '[header&header-1 # foo]');

  // Inline styles should be parsed inside headers
  MT('atxH1inline', '[header&header-1 # foo ][header&header-1&em *bar*]');

  MT('atxIndentedTooMuch', '[header&header-1 # foo]', '    [comment # bar]');

  // disable atx inside blockquote until we implement proper blockquote inner mode
  // TODO: fix to be CommonMark-compliant
  MT('atxNestedInsideBlockquote', '[quote&quote-1 > # foo]');

  MT('atxAfterBlockquote', '[quote&quote-1 > foo]', '[header&header-1 # bar]');

  // Setext headers - H1, H2
  // Per documentation, "Any number of underlining =’s or -’s will work."
  // http://daringfireball.net/projects/markdown/syntax#header
  // Ideally, the text would be marked as `header` as well, but this is
  // not really feasible at the moment. So, instead, we're testing against
  // what works today, to avoid any regressions.
  //
  // Check if single underlining = works
  MT('setextH1', '[header&header-1 foo]', '[header&header-1 =]');

  // Check if 3+ ='s work
  MT('setextH1', '[header&header-1 foo]', '[header&header-1 ===]');

  // Check if single underlining - works
  MT('setextH2', '[header&header-2 foo]', '[header&header-2 -]');

  // Check if 3+ -'s work
  MT('setextH2', '[header&header-2 foo]', '[header&header-2 ---]');

  // http://spec.commonmark.org/0.19/#example-45
  MT(
    'setextH2AllowSpaces',
    '[header&header-2 foo]',
    '   [header&header-2 ----      ]',
  );

  // http://spec.commonmark.org/0.19/#example-44
  MT('noSetextAfterIndentedCodeBlock', '     [comment foo]', '[hr ---]');

  MT(
    'setextAfterFencedCode',
    '[comment ```]',
    '[comment foo]',
    '[comment ```]',
    '[header&header-2 bar]',
    '[header&header-2 ---]',
  );

  MT(
    'setextAferATX',
    '[header&header-1 # foo]',
    '[header&header-2 bar]',
    '[header&header-2 ---]',
  );

  // http://spec.commonmark.org/0.19/#example-51
  MT(
    'noSetextAfterQuote',
    '[quote&quote-1 > foo]',
    '[hr ---]',
    '',
    '[quote&quote-1 > foo]',
    '[quote&quote-1 bar]',
    '[hr ---]',
  );

  MT('noSetextAfterList', '[variable-2 - foo]', '[hr ---]');

  MT(
    'noSetextAfterList_listContinuation',
    '[variable-2 - foo]',
    'bar',
    '[hr ---]',
  );

  MT(
    'setextAfterList_afterIndentedCode',
    '[variable-2 - foo]',
    '',
    '      [comment bar]',
    '[header&header-2 baz]',
    '[header&header-2 ---]',
  );

  MT(
    'setextAfterList_afterFencedCodeBlocks',
    '[variable-2 - foo]',
    '',
    '      [comment ```]',
    '      [comment bar]',
    '      [comment ```]',
    '[header&header-2 baz]',
    '[header&header-2 ---]',
  );

  MT(
    'setextAfterList_afterHeader',
    '[variable-2 - foo]',
    '  [variable-2&header&header-1 # bar]',
    '[header&header-2 baz]',
    '[header&header-2 ---]',
  );

  MT(
    'setextAfterList_afterHr',
    '[variable-2 - foo]',
    '',
    '  [hr ---]',
    '[header&header-2 bar]',
    '[header&header-2 ---]',
  );

  MT(
    'setext_nestedInlineMarkup',
    '[header&header-1 foo ][em&header&header-1 *bar*]',
    '[header&header-1 =]',
  );

  MT(
    'setext_linkDef',
    "[link [[aaa]]:] [string&url http://google.com 'title']",
    '[hr ---]',
  );

  // currently, looks max one line ahead, thus won't catch valid CommonMark
  //  markup
  MT(
    'setext_oneLineLookahead',
    'foo',
    '[header&header-1 bar]',
    '[header&header-1 =]',
  );

  // ensure we don't regard space after dash as a list
  MT(
    'setext_emptyList',
    '[header&header-2 foo]',
    '[header&header-2 - ]',
    'foo',
  );

  // Single-line blockquote with trailing space
  MT('blockquoteSpace', '[quote&quote-1 > foo]');

  // Single-line blockquote
  MT('blockquoteNoSpace', '[quote&quote-1 >foo]');

  // No blank line before blockquote
  MT('blockquoteNoBlankLine', 'foo', '[quote&quote-1 > bar]');

  MT(
    'blockquoteNested',
    '[quote&quote-1 > foo]',
    '[quote&quote-1 >][quote&quote-2 > foo]',
    '[quote&quote-1 >][quote&quote-2 >][quote&quote-3 > foo]',
  );

  // ensure quote-level is inferred correctly even if indented
  MT(
    'blockquoteNestedIndented',
    ' [quote&quote-1 > foo]',
    ' [quote&quote-1 >][quote&quote-2 > foo]',
    ' [quote&quote-1 >][quote&quote-2 >][quote&quote-3 > foo]',
  );

  // ensure quote-level is inferred correctly even if indented
  MT('blockquoteIndentedTooMuch', 'foo', '    > bar');

  // Single-line blockquote followed by normal paragraph
  MT('blockquoteThenParagraph', '[quote&quote-1 >foo]', '', 'bar');

  // Multi-line blockquote (lazy mode)
  MT('multiBlockquoteLazy', '[quote&quote-1 >foo]', '[quote&quote-1 bar]');

  // Multi-line blockquote followed by normal paragraph (lazy mode)
  MT(
    'multiBlockquoteLazyThenParagraph',
    '[quote&quote-1 >foo]',
    '[quote&quote-1 bar]',
    '',
    'hello',
  );

  // Multi-line blockquote (non-lazy mode)
  MT('multiBlockquote', '[quote&quote-1 >foo]', '[quote&quote-1 >bar]');

  // Multi-line blockquote followed by normal paragraph (non-lazy mode)
  MT(
    'multiBlockquoteThenParagraph',
    '[quote&quote-1 >foo]',
    '[quote&quote-1 >bar]',
    '',
    'hello',
  );

  // disallow lists inside blockquote for now because it causes problems outside blockquote
  // TODO: fix to be CommonMark-compliant
  MT('listNestedInBlockquote', '[quote&quote-1 > - foo]');

  // disallow fenced blocks inside blockquote because it causes problems outside blockquote
  // TODO: fix to be CommonMark-compliant
  MT(
    'fencedBlockNestedInBlockquote',
    '[quote&quote-1 > ```]',
    '[quote&quote-1 > code]',
    '[quote&quote-1 > ```]',
    // ensure we still allow inline code
    '[quote&quote-1 > ][quote&quote-1&comment `code`]',
  );

  // Header with leading space after continued blockquote (#3287, negative indentation)
  MT(
    'headerAfterContinuedBlockquote',
    '[quote&quote-1 > foo]',
    '[quote&quote-1 bar]',
    '',
    ' [header&header-1 # hello]',
  );

  // Check list types

  MT(
    'listAsterisk',
    'foo',
    'bar',
    '',
    '[variable-2 * foo]',
    '[variable-2 * bar]',
  );

  MT('listPlus', 'foo', 'bar', '', '[variable-2 + foo]', '[variable-2 + bar]');

  MT('listDash', 'foo', 'bar', '', '[variable-2 - foo]', '[variable-2 - bar]');

  MT(
    'listNumber',
    'foo',
    'bar',
    '',
    '[variable-2 1. foo]',
    '[variable-2 2. bar]',
  );

  MT(
    'listFromParagraph',
    'foo',
    '[variable-2 1. bar]',
    '[variable-2 2. hello]',
  );

  // List after hr
  MT('listAfterHr', '[hr ---]', '[variable-2 - bar]');

  // List after header
  MT('listAfterHeader', '[header&header-1 # foo]', '[variable-2 - bar]');

  // hr after list
  MT('hrAfterList', '[variable-2 - foo]', '[hr -----]');

  MT(
    'hrAfterFencedCode',
    '[comment ```]',
    '[comment code]',
    '[comment ```]',
    '[hr ---]',
  );

  // allow hr inside lists
  // (require prev line to be empty or hr, TODO: non-CommonMark-compliant)
  MT(
    'hrInsideList',
    '[variable-2 - foo]',
    '',
    '  [hr ---]',
    '     [hr ---]',
    '',
    '      [comment ---]',
  );

  MT('consecutiveHr', '[hr ---]', '[hr ---]', '[hr ---]');

  // Formatting in lists (*)
  MT(
    'listAsteriskFormatting',
    '[variable-2 * ][variable-2&em *foo*][variable-2  bar]',
    '[variable-2 * ][variable-2&strong **foo**][variable-2  bar]',
    '[variable-2 * ][variable-2&em&strong ***foo***][variable-2  bar]',
    '[variable-2 * ][variable-2&comment `foo`][variable-2  bar]',
  );

  // Formatting in lists (+)
  MT(
    'listPlusFormatting',
    '[variable-2 + ][variable-2&em *foo*][variable-2  bar]',
    '[variable-2 + ][variable-2&strong **foo**][variable-2  bar]',
    '[variable-2 + ][variable-2&em&strong ***foo***][variable-2  bar]',
    '[variable-2 + ][variable-2&comment `foo`][variable-2  bar]',
  );

  // Formatting in lists (-)
  MT(
    'listDashFormatting',
    '[variable-2 - ][variable-2&em *foo*][variable-2  bar]',
    '[variable-2 - ][variable-2&strong **foo**][variable-2  bar]',
    '[variable-2 - ][variable-2&em&strong ***foo***][variable-2  bar]',
    '[variable-2 - ][variable-2&comment `foo`][variable-2  bar]',
  );

  // Formatting in lists (1.)
  MT(
    'listNumberFormatting',
    '[variable-2 1. ][variable-2&em *foo*][variable-2  bar]',
    '[variable-2 2. ][variable-2&strong **foo**][variable-2  bar]',
    '[variable-2 3. ][variable-2&em&strong ***foo***][variable-2  bar]',
    '[variable-2 4. ][variable-2&comment `foo`][variable-2  bar]',
  );

  // Paragraph lists
  MT('listParagraph', '[variable-2 * foo]', '', '[variable-2 * bar]');

  // Multi-paragraph lists
  //
  // 4 spaces
  MT(
    'listMultiParagraph',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    '    [variable-2 hello]',
  );

  // 4 spaces, extra blank lines (should still be list, per Dingus)
  MT(
    'listMultiParagraphExtra',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    '',
    '    [variable-2 hello]',
  );

  // 4 spaces, plus 1 space (should still be list, per Dingus)
  MT(
    'listMultiParagraphExtraSpace',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    '     [variable-2 hello]',
    '',
    '    [variable-2 world]',
  );

  // 1 tab
  MT(
    'listTab',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    '\t[variable-2 hello]',
  );

  // No indent
  MT(
    'listNoIndent',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    'hello',
  );

  MT(
    'listCommonMarkIndentationCode',
    '[variable-2 * Code blocks also affect]',
    '  [variable-3 * The next level starts where the contents start.]',
    '   [variable-3 *    Anything less than that will keep the item on the same level.]',
    '       [variable-3 * Each list item can indent the first level further and further.]',
    '  [variable-3 * For the most part, this makes sense while writing a list.]',
    '    [keyword * This means two items with same indentation can be different levels.]',
    '     [keyword *  Each level has an indent requirement that can change between items.]',
    '       [keyword * A list item that meets this will be part of the next level.]',
    '   [variable-3 * Otherwise, it will be part of the level where it does meet this.]',
    ' [variable-2 * World]',
  );

  // should handle nested and un-nested lists
  MT(
    'listCommonMark_MixedIndents',
    '[variable-2 * list1]',
    '    [variable-2 list1]',
    '  [variable-2&header&header-1 # heading still part of list1]',
    '  [variable-2 text after heading still part of list1]',
    '',
    '      [comment indented codeblock]',
    '  [variable-2 list1 after code block]',
    '  [variable-3 * list2]',
    // amount of spaces on empty lines between lists doesn't matter
    '              ',
    // extra empty lines irrelevant
    '',
    '',
    '    [variable-3 indented text part of list2]',
    '    [keyword * list3]',
    '',
    '    [variable-3 text at level of list2]',
    '',
    '  [variable-2 de-indented text part of list1 again]',
    '',
    '  [variable-2&comment ```]',
    '  [comment code]',
    '  [variable-2&comment ```]',
    '',
    '  [variable-2 text after fenced code]',
  );

  // should correctly parse numbered list content indentation
  MT(
    'listCommonMark_NumeberedListIndent',
    '[variable-2 1000. list with base indent of 6]',
    '',
    '      [variable-2 text must be indented 6 spaces at minimum]',
    '',
    '         [variable-2 9-spaces indented text still part of list]',
    '',
    '          [comment indented codeblock starts at 10 spaces]',
    '',
    '     [comment text indented by 5 spaces no longer belong to list]',
  );

  // should consider tab as 4 spaces
  MT(
    'listCommonMark_TabIndented',
    '[variable-2 * list]',
    '\t[variable-3 * list2]',
    '',
    '\t\t[variable-3 part of list2]',
  );

  MT('listAfterBlockquote', '[quote&quote-1 > foo]', '[variable-2 - bar]');

  // shouldn't create sublist if it's indented more than allowed
  MT(
    'nestedListIndentedTooMuch',
    '[variable-2 - foo]',
    '          [variable-2 - bar]',
  );

  MT('listIndentedTooMuchAfterParagraph', 'foo', '    - bar');

  // Blockquote
  MT(
    'blockquote',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    '    [variable-2&quote&quote-1 > hello]',
  );

  // Code block
  MT(
    'blockquoteCode',
    '[variable-2 * foo]',
    '',
    '[variable-2 * bar]',
    '',
    '        [comment > hello]',
    '',
    '    [variable-2 world]',
  );

  // Code block followed by text
  MT(
    'blockquoteCodeText',
    '[variable-2 * foo]',
    '',
    '    [variable-2 bar]',
    '',
    '        [comment hello]',
    '',
    '    [variable-2 world]',
  );

  // Nested list

  MT('listAsteriskNested', '[variable-2 * foo]', '', '    [variable-3 * bar]');

  MT('listPlusNested', '[variable-2 + foo]', '', '    [variable-3 + bar]');

  MT('listDashNested', '[variable-2 - foo]', '', '    [variable-3 - bar]');

  MT('listNumberNested', '[variable-2 1. foo]', '', '    [variable-3 2. bar]');

  MT(
    'listMixed',
    '[variable-2 * foo]',
    '',
    '    [variable-3 + bar]',
    '',
    '        [keyword - hello]',
    '',
    '            [variable-2 1. world]',
  );

  MT(
    'listBlockquote',
    '[variable-2 * foo]',
    '',
    '    [variable-3 + bar]',
    '',
    '        [quote&quote-1&variable-3 > hello]',
  );

  MT(
    'listCode',
    '[variable-2 * foo]',
    '',
    '    [variable-3 + bar]',
    '',
    '            [comment hello]',
  );

  // Code with internal indentation
  MT(
    'listCodeIndentation',
    '[variable-2 * foo]',
    '',
    '        [comment bar]',
    '            [comment hello]',
    '                [comment world]',
    '        [comment foo]',
    '    [variable-2 bar]',
  );

  // List nesting edge cases
  MT(
    'listNested',
    '[variable-2 * foo]',
    '',
    '    [variable-3 * bar]',
    '',
    '       [variable-3 hello]',
  );
  MT(
    'listNested',
    '[variable-2 * foo]',
    '',
    '    [variable-3 * bar]',
    '',
    '      [keyword * foo]',
  );

  // Code followed by text
  MT(
    'listCodeText',
    '[variable-2 * foo]',
    '',
    '        [comment bar]',
    '',
    'hello',
  );

  // Following tests directly from official Markdown documentation
  // http://daringfireball.net/projects/markdown/syntax#hr

  MT('hrSpace', '[hr * * *]');

  MT('hr', '[hr ***]');

  MT('hrLong', '[hr *****]');

  MT('hrSpaceDash', '[hr - - -]');

  MT('hrDashLong', '[hr ---------------------------------------]');

  //Images
  MT(
    'Images',
    '[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://link.to/image.jpg)]',
  );

  //Images with highlight alt text
  MT(
    'imageEm',
    '[image&image-marker !][image&image-alt-text&link [[][image-alt-text&em&image&link *alt text*][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]',
  );

  MT(
    'imageStrong',
    '[image&image-marker !][image&image-alt-text&link [[][image-alt-text&strong&image&link **alt text**][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]',
  );

  MT(
    'imageEmStrong',
    '[image&image-marker !][image&image-alt-text&link [[][image&image-alt-text&em&strong&link ***alt text***][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]',
  );

  // Inline link with title
  MT(
    'linkTitle',
    '[link [[foo]]][string&url (http://example.com/ "bar")] hello',
  );

  // Inline link without title
  MT('linkNoTitle', '[link [[foo]]][string&url (http://example.com/)] bar');

  // Inline link with image
  MT(
    'linkImage',
    '[link [[][link&image&image-marker !][link&image&image-alt-text&link [[alt text]]][string&url (http://link.to/image.jpg)][link ]]][string&url (http://example.com/)] bar',
  );

  // Inline link with Em
  MT(
    'linkEm',
    '[link [[][link&em *foo*][link ]]][string&url (http://example.com/)] bar',
  );

  // Inline link with Strong
  MT(
    'linkStrong',
    '[link [[][link&strong **foo**][link ]]][string&url (http://example.com/)] bar',
  );

  // Inline link with EmStrong
  MT(
    'linkEmStrong',
    '[link [[][link&em&strong ***foo***][link ]]][string&url (http://example.com/)] bar',
  );

  MT(
    'multilineLink',
    '[link [[foo]',
    '[link bar]]][string&url (https://foo#_a)]',
    'should not be italics',
  );

  // Image with title
  MT(
    'imageTitle',
    '[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://example.com/ "bar")] hello',
  );

  // Image without title
  MT(
    'imageNoTitle',
    '[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://example.com/)] bar',
  );

  // Image with asterisks
  MT(
    'imageAsterisks',
    '[image&image-marker !][image&image-alt-text&link [[ ][image&image-alt-text&em&link *alt text*][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)] bar',
  );

  // Not a link. Should be normal text due to square brackets being used
  // regularly in text, especially in quoted material, and no space is allowed
  // between square brackets and parentheses (per Dingus).
  MT('notALink', '[link [[foo]]] (bar)');

  // Reference-style links
  MT('linkReference', '[link [[foo]]][string&url [[bar]]] hello');

  // Reference-style links with Em
  MT(
    'linkReferenceEm',
    '[link [[][link&em *foo*][link ]]][string&url [[bar]]] hello',
  );

  // Reference-style links with Strong
  MT(
    'linkReferenceStrong',
    '[link [[][link&strong **foo**][link ]]][string&url [[bar]]] hello',
  );

  // Reference-style links with EmStrong
  MT(
    'linkReferenceEmStrong',
    '[link [[][link&em&strong ***foo***][link ]]][string&url [[bar]]] hello',
  );

  // Reference-style links with optional space separator (per documentation)
  // "You can optionally use a space to separate the sets of brackets"
  MT('linkReferenceSpace', '[link [[foo]]] [string&url [[bar]]] hello');

  // Should only allow a single space ("...use *a* space...")
  MT('linkReferenceDoubleSpace', '[link [[foo]]]  [link [[bar]]] hello');

  // Reference-style links with implicit link name
  MT('linkImplicit', '[link [[foo]]][string&url [[]]] hello');

  // @todo It would be nice if, at some point, the document was actually
  // checked to see if the referenced link exists

  // Link label, for reference-style links (taken from documentation)

  MT('labelNoTitle', '[link [[foo]]:] [string&url http://example.com/]');

  MT('labelIndented', '   [link [[foo]]:] [string&url http://example.com/]');

  MT(
    'labelSpaceTitle',
    '[link [[foo bar]]:] [string&url http://example.com/ "hello"]',
  );

  MT(
    'labelDoubleTitle',
    '[link [[foo bar]]:] [string&url http://example.com/ "hello"] "world"',
  );

  MT(
    'labelTitleDoubleQuotes',
    '[link [[foo]]:] [string&url http://example.com/  "bar"]',
  );

  MT(
    'labelTitleSingleQuotes',
    "[link [[foo]]:] [string&url http://example.com/  'bar']",
  );

  MT(
    'labelTitleParentheses',
    '[link [[foo]]:] [string&url http://example.com/  (bar)]',
  );

  MT(
    'labelTitleInvalid',
    '[link [[foo]]:] [string&url http://example.com/] bar',
  );

  MT(
    'labelLinkAngleBrackets',
    '[link [[foo]]:] [string&url <http://example.com/>  "bar"]',
  );

  MT(
    'labelTitleNextDoubleQuotes',
    '[link [[foo]]:] [string&url http://example.com/]',
    '[string "bar"] hello',
  );

  MT(
    'labelTitleNextSingleQuotes',
    '[link [[foo]]:] [string&url http://example.com/]',
    "[string 'bar'] hello",
  );

  MT(
    'labelTitleNextParentheses',
    '[link [[foo]]:] [string&url http://example.com/]',
    '[string (bar)] hello',
  );

  MT(
    'labelTitleNextMixed',
    '[link [[foo]]:] [string&url http://example.com/]',
    '(bar" hello',
  );

  MT('labelEscape', '[link [[foo \\]] ]]:] [string&url http://example.com/]');

  MT(
    'labelEscapeColon',
    '[link [[foo \\]]: bar]]:] [string&url http://example.com/]',
  );

  MT('labelEscapeEnd', '\\[[foo\\]]: http://example.com/');

  MT('linkWeb', '[link <http://example.com/>] foo');

  MT(
    'linkWebDouble',
    '[link <http://example.com/>] foo [link <http://example.com/>]',
  );

  MT('linkEmail', '[link <user@example.com>] foo');

  MT(
    'linkEmailDouble',
    '[link <user@example.com>] foo [link <user@example.com>]',
  );

  MT('emAsterisk', '[em *foo*] bar');

  MT('emUnderscore', '[em _foo_] bar');

  MT('emInWordAsterisk', 'foo[em *bar*]hello');

  MT('emInWordUnderscore', 'foo_bar_hello');

  // Per documentation: "...surround an * or _ with spaces, it’ll be
  // treated as a literal asterisk or underscore."

  MT('emEscapedBySpaceIn', 'foo [em _bar _ hello_] world');

  MT('emEscapedBySpaceOut', 'foo _ bar [em _hello_] world');

  MT('emEscapedByNewline', 'foo', '_ bar [em _hello_] world');

  // Unclosed emphasis characters
  // Instead of simply marking as EM / STRONG, it would be nice to have an
  // incomplete flag for EM and STRONG, that is styled slightly different.
  MT('emIncompleteAsterisk', 'foo [em *bar]');

  MT('emIncompleteUnderscore', 'foo [em _bar]');

  MT('strongAsterisk', '[strong **foo**] bar');

  MT('strongUnderscore', '[strong __foo__] bar');

  MT('emStrongAsterisk', '[em *foo][em&strong **bar*][strong hello**] world');

  MT(
    'emStrongUnderscore',
    '[em _foo ][em&strong __bar_][strong  hello__] world',
  );

  // "...same character must be used to open and close an emphasis span.""
  MT('emStrongMixed', '[em _foo][em&strong **bar*hello__ world]');

  MT('emStrongMixed', '[em *foo ][em&strong __bar_hello** world]');

  MT('linkWithNestedParens', '[link [[foo]]][string&url (bar(baz))]');

  // These characters should be escaped:
  // \   backslash
  // `   backtick
  // *   asterisk
  // _   underscore
  // {}  curly braces
  // []  square brackets
  // ()  parentheses
  // #   hash mark
  // +   plus sign
  // -   minus sign (hyphen)
  // .   dot
  // !   exclamation mark

  MT('escapeBacktick', 'foo \\`bar\\`');

  MT('doubleEscapeBacktick', 'foo \\\\[comment `bar\\\\`]');

  MT('escapeAsterisk', 'foo \\*bar\\*');

  MT('doubleEscapeAsterisk', 'foo \\\\[em *bar\\\\*]');

  MT('escapeUnderscore', 'foo \\_bar\\_');

  MT('doubleEscapeUnderscore', 'foo \\\\[em _bar\\\\_]');

  MT('escapeHash', '\\# foo');

  MT('doubleEscapeHash', '\\\\# foo');

  MT('escapeNewline', '\\', '[em *foo*]');

  // Class override tests
  TokenTypeOverrideTest(
    'overrideHeader1',
    '[override-header&override-header-1 # Foo]',
  );

  TokenTypeOverrideTest(
    'overrideHeader2',
    '[override-header&override-header-2 ## Foo]',
  );

  TokenTypeOverrideTest(
    'overrideHeader3',
    '[override-header&override-header-3 ### Foo]',
  );

  TokenTypeOverrideTest(
    'overrideHeader4',
    '[override-header&override-header-4 #### Foo]',
  );

  TokenTypeOverrideTest(
    'overrideHeader5',
    '[override-header&override-header-5 ##### Foo]',
  );

  TokenTypeOverrideTest(
    'overrideHeader6',
    '[override-header&override-header-6 ###### Foo]',
  );

  TokenTypeOverrideTest('overrideCode', '[override-code `foo`]');

  TokenTypeOverrideTest(
    'overrideCodeBlock',
    '[override-code ```]',
    '[override-code foo]',
    '[override-code ```]',
  );

  TokenTypeOverrideTest(
    'overrideQuote',
    '[override-quote&override-quote-1 > foo]',
    '[override-quote&override-quote-1 > bar]',
  );

  TokenTypeOverrideTest(
    'overrideQuoteNested',
    '[override-quote&override-quote-1 > foo]',
    '[override-quote&override-quote-1 >][override-quote&override-quote-2 > bar]',
    '[override-quote&override-quote-1 >][override-quote&override-quote-2 >][override-quote&override-quote-3 > baz]',
  );

  TokenTypeOverrideTest(
    'overrideLists',
    '[override-list1 - foo]',
    '',
    '    [override-list2 + bar]',
    '',
    '        [override-list3 * baz]',
    '',
    '            [override-list1 1. qux]',
    '',
    '                [override-list2 - quux]',
  );

  TokenTypeOverrideTest('overrideHr', '[override-hr * * *]');

  TokenTypeOverrideTest(
    'overrideImage',
    '[override-image&override-image-marker !][override-image&override-image-alt-text&link [[alt text]]][override-link-href&url (http://link.to/image.jpg)]',
  );

  TokenTypeOverrideTest(
    'overrideLinkText',
    '[override-link-text [[foo]]][override-link-href&url (http://example.com)]',
  );

  TokenTypeOverrideTest(
    'overrideLinkEmailAndInline',
    '[override-link-email <][override-link-inline foo@example.com>]',
  );

  TokenTypeOverrideTest('overrideEm', '[override-em *foo*]');

  TokenTypeOverrideTest('overrideStrong', '[override-strong **foo**]');

  TokenTypeOverrideTest(
    'overrideStrikethrough',
    '[override-strikethrough ~~foo~~]',
  );

  TokenTypeOverrideTest('overrideEmoji', '[override-emoji :foo:]');

  FormatTokenTypeOverrideTest(
    'overrideFormatting',
    '[override-formatting-escape \\*]',
  );

  // Tests to make sure GFM-specific things aren't getting through

  MT('taskList', '[variable-2 * ][link&variable-2 [[ ]]][variable-2 bar]');

  MT(
    'fencedCodeBlocks',
    '[comment ```]',
    '[comment foo]',
    '',
    '[comment bar]',
    '[comment ```]',
    'baz',
  );

  MT(
    'fencedCodeBlocks_invalidClosingFence_trailingText',
    '[comment ```]',
    '[comment foo]',
    '[comment ``` must not have trailing text]',
    '[comment baz]',
  );

  MT(
    'fencedCodeBlocks_invalidClosingFence_trailingTabs',
    '[comment ```]',
    '[comment foo]',
    '[comment ```\t]',
    '[comment baz]',
  );

  MT(
    'fencedCodeBlocks_validClosingFence',
    '[comment ```]',
    '[comment foo]',
    // may have trailing spaces
    '[comment ```     ]',
    'baz',
  );

  MT(
    'fencedCodeBlocksInList_closingFenceIndented',
    '[variable-2 - list]',
    '    [variable-2&comment ```]',
    '    [comment foo]',
    '     [variable-2&comment ```]',
    '    [variable-2 baz]',
  );

  MT(
    'fencedCodeBlocksInList_closingFenceIndentedTooMuch',
    '[variable-2 - list]',
    '    [variable-2&comment ```]',
    '    [comment foo]',
    '      [comment ```]',
    '    [comment baz]',
  );

  MT(
    'fencedCodeBlockModeSwitching',
    '[comment ```javascript]',
    '[variable foo]',
    '',
    '[comment ```]',
    'bar',
  );

  MT_noFencedHighlight(
    'fencedCodeBlock_noHighlight',
    '[comment ```javascript]',
    '[comment foo]',
    '[comment ```]',
  );

  MT(
    'fencedCodeBlockModeSwitchingObjc',
    '[comment ```objective-c]',
    '[keyword @property] [variable NSString] [operator *] [variable foo];',
    '[comment ```]',
    'bar',
  );

  MT(
    'fencedCodeBlocksMultipleChars',
    '[comment `````]',
    '[comment foo]',
    '[comment ```]',
    '[comment foo]',
    '[comment `````]',
    'bar',
  );

  MT(
    'fencedCodeBlocksTildes',
    '[comment ~~~]',
    '[comment foo]',
    '[comment ~~~]',
    'bar',
  );

  MT(
    'fencedCodeBlocksTildesMultipleChars',
    '[comment ~~~~~]',
    '[comment ~~~]',
    '[comment foo]',
    '[comment ~~~~~]',
    'bar',
  );

  MT(
    'fencedCodeBlocksMultipleChars',
    '[comment `````]',
    '[comment foo]',
    '[comment ```]',
    '[comment foo]',
    '[comment `````]',
    'bar',
  );

  MT(
    'fencedCodeBlocksMixed',
    '[comment ~~~]',
    '[comment ```]',
    '[comment foo]',
    '[comment ~~~]',
    'bar',
  );

  MT(
    'fencedCodeBlocksAfterBlockquote',
    '[quote&quote-1 > foo]',
    '[comment ```]',
    '[comment bar]',
    '[comment ```]',
  );

  // fencedCode indented too much should act as simple indentedCode
  //  (hence has no highlight formatting)
  FT(
    'tooMuchIndentedFencedCode',
    '    [comment ```]',
    '    [comment code]',
    '    [comment ```]',
  );

  MT(
    'autoTerminateFencedCodeWhenLeavingList',
    '[variable-2 - list1]',
    '  [variable-3 - list2]',
    '    [variable-3&comment ```]',
    '    [comment code]',
    '  [variable-3 - list2]',
    '  [variable-2&comment ```]',
    '  [comment code]',
    '[quote&quote-1 > foo]',
  );

  // Tests that require XML mode

  MT(
    'xmlMode',
    '[tag&bracket <][tag div][tag&bracket >]',
    '  *foo*',
    '  [tag&bracket <][tag http://github.com][tag&bracket />]',
    '[tag&bracket </][tag div][tag&bracket >]',
    '[link <http://github.com/>]',
  );

  MT(
    'xmlModeWithMarkdownInside',
    '[tag&bracket <][tag div] [attribute markdown]=[string 1][tag&bracket >]',
    '[em *foo*]',
    '[link <http://github.com/>]',
    '[tag </div>]',
    '[link <http://github.com/>]',
    '[tag&bracket <][tag div][tag&bracket >]',
    '[tag&bracket </][tag div][tag&bracket >]',
  );

  MT_noXml('xmlHighlightDisabled', '<div>foo</div>');
})();
